package com.decent.common.oss.util;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.model.*;
import com.decent.common.enums.ErrorCodeEnum;
import com.decent.common.exception.ErrorCodeException;
import com.decent.common.oss.config.AbstractOssConfig;
import com.decent.common.oss.constants.OssConstants;
import com.decent.common.oss.enums.OssFolderEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

/**
 * 阿里云oss对象存储工具类
 *
 * @author wangyx
 */
@Slf4j
public final class OssUtil {
    /**
     * 阿里云OSS内网客户端
     */
    private static OSSClient innerOssClient;

    /**
     * 阿里云OSS普通客户端
     */
    private static OSSClient ossClient;

    /**
     * 获取内网客户端
     *
     * @return 客户端
     */
    private static OSSClient getInnerOssClient() {
        if (ObjectUtil.isNotNull(innerOssClient)) {
            return innerOssClient;
        }
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        return innerOssClient = new OSSClient(config.getInnerEndPoint(),
                new DefaultCredentialProvider(config.getAccessKeyId(), config.getAccessKeySecret()), null);
    }

    /**
     * 获取默认客户端
     *
     * @return 客户端
     */
    private static OSSClient getOssClient() {
        if (ObjectUtil.isNotNull(ossClient)) {
            return ossClient;
        }
        AbstractOssConfig ossConfig = AbstractOssConfig.createOssConfig();
        return ossClient = new OSSClient(ossConfig.getEndPoint(),
                new DefaultCredentialProvider(ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret()), null);
    }

    /**
     * 设置并返回文件公共读链接
     *
     * @param ossPath 文件路径
     * @return 公共读链接
     */
    private static String getPublicUrl(String ossPath) {
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        //设置单个key 为公共读
        OssUtil.getInnerOssClient().setObjectAcl(config.getBucketName(), StrUtil.removePrefix(ossPath, "/"),
                CannedAccessControlList.PublicRead);
        //返回路径
        return config.getPublicEndPoint() + (StrUtil.startWith(ossPath, "/") ? ossPath : "/" + ossPath);
    }

    /**
     * 生成图片返显url
     *
     * @param ossPath    图片key
     * @param expiration 过期时间
     * @return url
     */
    public static String getPicUrl(String ossPath, LocalDateTime expiration) {
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(config.getBucketName(), ossPath,
                HttpMethod.GET);
        Instant instant = expiration.atZone(ZoneId.systemDefault()).toInstant();
        req.setExpiration(Date.from(instant));
        URL signedUrl = OssUtil.getOssClient().generatePresignedUrl(req);
        return signedUrl.toString();
    }

    /**
     * 删除文件
     *
     * @param ossPath 文件路径
     */
    public static void deleteFile(String ossPath) {
        if (StrUtil.isBlank(ossPath)) {
            return;
        }
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        try {
            OssUtil.getInnerOssClient().deleteObject(config.getBucketName(), ossPath);
        } catch (Exception ex) {
            log.warn("删除阿里云文件异常,ossPath=[{}]", ossPath, ex);
        }
    }

    /**
     * 文件上传到oss中
     *
     * @param multipartFile  文件
     * @param folderName     文件夹名称
     * @param fileName       文件名称
     * @param needPublicRead true：需要公共读；false：不需要公共读；
     * @return Tuple2<String, String> <oss存储路径, 公共读链接（不需要公共读时返回oss存储路径）>
     */
    public static Tuple2<String, String> submitFile(MultipartFile multipartFile, OssFolderEnum folderName,
                                                    String fileName, boolean needPublicRead) {
        if (ObjectUtil.isNull(multipartFile)) {
            throw new ErrorCodeException(ErrorCodeEnum.INVALID_PARAMS);
        }
        // 大小校验
        if (multipartFile.getSize() > OssConstants.MAX_FILE_SIZE) {
            throw new ErrorCodeException(ErrorCodeEnum.INVALID_PARAMS, "图片太大了");
        }
        // 文件格式校验
        if (OssConstants.ALLOW_FILE_SUFFIX.stream()
                .anyMatch(suffix -> StrUtil.equalsIgnoreCase(suffix, multipartFile.getOriginalFilename()))) {
            throw new ErrorCodeException(ErrorCodeEnum.INVALID_PARAMS, "不支持此类型的文件上传");
        }
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        try {
            // oss文件路径格式 = oss基础路径/自定义文件夹名/上传日期/自定义文件名或时间戳文件名
            String ossPath = config.getBasePath() + "/" + folderName + "/" +
                    LocalDateTimeUtil.format(LocalDate.now(), DatePattern.NORM_DATE_PATTERN) + "/" +
                    Opt.ofNullable(fileName).orElseGet(() -> String.valueOf(System.nanoTime()));
            // 内网客户端上传
            OssUtil.getInnerOssClient().putObject(new PutObjectRequest(config.getBucketName(), ossPath,
                    multipartFile.getInputStream(), new ObjectMetadata()));
            // 按需求判断是否设置公共读
            return Tuples.of(ossPath, needPublicRead ? OssUtil.getPublicUrl(ossPath) : ossPath);
        } catch (IOException e) {
            log.error("文件上传到oss中异常,folderName=[{}],fileName=[{}]", folderName, fileName, e);
            throw new ErrorCodeException(ErrorCodeEnum.ERROR, "文件上传异常");
        }
    }

    /**
     * 读取oss文件
     *
     * @param ossPath 文件路径
     * @param file    用于接收的文件
     */
    public static void getOssFile(String ossPath, File file) {
        AbstractOssConfig config = AbstractOssConfig.createOssConfig();
        OssUtil.getOssClient().getObject(new GetObjectRequest(config.getBucketName(), ossPath), file);
    }
}
